package nexus.criterios.exp;

import java.util.*;

public class Expresion{
	private HashMap<String,Double> valores;
	private Stack<Double> operando;
	private Stack<Character> operador;
	private String cadena;

	public Expresion(HashMap<String,Double> v,String c){
		cadena=c.replace("MAX[","[");
		valores=v;
		operando=new Stack<Double>();
		operador=new Stack<Character>();
	}

	public double evaluar()  throws ExpresionIncompleta,ErrorDeSintaxisExpresion,FaltaComa,FaltaAbre,FaltaOperador{
		while(hasNext()){
			char c=cadena.charAt(0);
			if(operador(c)){
				if(abre(c)){
					operador.push(c);
				}else if(cierra(c)){
					desapilar(c);
				}else{
					if(operador.isEmpty()){
						operador.push(c);
					}else if(c==','){
						operador.push(c);
					}else if(operador.peek()!=','&&precendencia(operador.peek())>precendencia(c)){
						if(operando.isEmpty())throw new ExpresionIncompleta();
						double b=operando.pop();
						if(operando.isEmpty())throw new ExpresionIncompleta();
						double a=operando.pop();
						double resultado=operar(b,a,operador.pop());
						operando.push(resultado);
						operador.push(c);
					}else{
						operador.push(c);
					}
				}
				cadena=cadena.substring(1);
			}else{
				double x;
				if(digito(c)){
					x=nextDouble();
				}else{
					String v=nextVariable();
					x=valores.get(v);
				}
				operando.push(x);
			}

		}
		while(!operador.isEmpty()){
			if(operando.isEmpty())throw new ExpresionIncompleta();
			double b=operando.pop();
			if(operando.isEmpty())throw new ExpresionIncompleta();
			double a=operando.pop();
			double resultado=operar(b,a,operador.pop());
			operando.push(resultado);
		}

		if(operando.size()>1)throw new FaltaOperador();
		return operando.pop();
	}

	private double operar(double b, double a, char c) throws ErrorDeSintaxisExpresion{
		double x=0;
		switch (c) {
		case '/':
			x=a/b;
			break;
		case '*':
			x=a*b;
			break;
		case '+':
			x=a+b;
			break;
		case '-':
			x=a-b;
			break;
		default:
			throw new ErrorDeSintaxisExpresion();
		}
		return x;
	}
	private void desapilar(char c) throws ExpresionIncompleta,ErrorDeSintaxisExpresion,FaltaComa,FaltaAbre{
		if(max(c)){
			double e1=evaExp1();
			double e2=evaExp2();
			operando.push(Math.max(e1,e2));
		}else{
			char o=operador.pop();
			while(!complementan(o,c)){
				if(operando.isEmpty())throw new ExpresionIncompleta();
				double b=operando.pop();
				if(operando.isEmpty())throw new ExpresionIncompleta();
				double a=operando.pop();
				operando.push(operar(a,b,o));
				if(operador.isEmpty())throw new FaltaAbre();
				o=operador.pop();
			}
		}
	}
	public Double evaExp1()  throws ExpresionIncompleta,ErrorDeSintaxisExpresion,FaltaComa{
		Character o=operador.pop();
		while(o!=','){
			if(operando.isEmpty())throw new ExpresionIncompleta();
			double b=operando.pop();
			if(operando.isEmpty())throw new ExpresionIncompleta();
			double a=operando.pop();
			operando.push(operar(a,b,o));
			if(operador.isEmpty())throw new FaltaComa();
			o=operador.pop();
			if(o=='[')throw new FaltaComa();
		}
		if(operando.isEmpty())throw new ExpresionIncompleta();
		return operando.pop();
	}
	public Double evaExp2()  throws ExpresionIncompleta,ErrorDeSintaxisExpresion,FaltaAbre{
		Character o=operador.pop();
		while(o!='['){
			if(operando.isEmpty())throw new ExpresionIncompleta();
			double b=operando.pop();
			if(operando.isEmpty())throw new ExpresionIncompleta();
			double a=operando.pop();
			operando.push(operar(a,b,o));
			if(operador.isEmpty())throw new FaltaAbre();
			o=operador.pop();
		}
		if(operando.isEmpty())throw new ExpresionIncompleta();
		return operando.pop();
	}
	public boolean max(char c){
		return c==']';
	}
	public boolean digito(char c){
		return c>='0'&&c<='9';
	}
	public boolean hasNext(){
		return cadena.length()>0;
	}
	public String nextVariable(){
		String res="";
		boolean fin=false;
		int i=0;
		for(;(i<cadena.length())&&!fin;i++){
			if(operador(cadena.charAt(i))){
				fin=true;
			}else{
				res+=cadena.charAt(i);
			}
		}
		if(fin)
		cadena=cadena.substring(i-1,cadena.length());
		else
		cadena="";
		return res;
	}
	public double nextDouble(){
		String v=nextVariable();
		return Double.parseDouble(v);
	}
	public boolean operador(char c){
		char[] x=new char[]{'+','*','/','-','(',')','[',']',','};
		boolean encontrado=false;
		int i=0;
		while(i<x.length&&!encontrado){
			if(x[i]==c)encontrado=true;
			i++;
		}
		return encontrado;
	}
	public boolean abre(char c){
		return c=='('||c=='[';
	}
	public boolean cierra(char c){
		return c==')'||c==']';
	}
	public boolean complementan(char a,char b){
		return (a=='('&&b==')')||(a=='['&&b==']');
	}
	public int precendencia(char c){
		int pre=100;
		if(c=='+'||c=='-')pre=0;
		return pre;
	}
}
